#pragma once

#include <iostream>
#include <memory>
#include <mutex>

#include <cstdio>
#include <ctime>
#include <stdarg.h>

#include <pthread.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


class Log
{
public:
    enum Msg_Level
    {
        Info = 0,
        Debug = 1,
        Warning = 2,
        Error = 3,
        Fatal = 4
    };
    enum Log_File
    {
        Screen = 0,
        OneFile = 1,
        Class = 2
    };
private:
    const char* const LOG_FILE = "log.txt";
    static constexpr int LOG_SIZE = 1024;
private:
    const char* level_to_string(int level)
    {
        static const char *str[] = {"Info", "Debug", "Warning", "Error", "Fatal"};
        return str[level];
    }
public:
    Log(int method = 0, const std::string& path = "./log/")
        : _print_method(method)
        , _print_path(path)
    {}

    void change_method(int method)
    {
        _print_method = method;
    }
    void change_path(const std::string& path)
    {
        _print_path = path;
    }

    void operator()(int level, const char* format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);

        char defaultbuffer[LOG_SIZE] = {0};
        char name[64] = { 0 };
        pthread_t tid = pthread_self();
        if (pthread_getname_np(tid, name, sizeof(name)) != 0)
            sprintf(name, "%lu", ((unsigned long)tid));
        snprintf(defaultbuffer, sizeof(defaultbuffer), "[%s][%d-%d-%d %d:%d:%d][%s]:", level_to_string(level),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec,
                 name);
        char rightbuffer[LOG_SIZE] = {0};
        va_list arg;
        va_start(arg, format);
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, arg);
        va_end(arg);

        char logtxt[LOG_SIZE * 2] = {0};
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", defaultbuffer, rightbuffer);
        print_log(level, logtxt);
    }

    ~Log()
    {
        _print_method = 0;
    }

private:
    void print_log(int level, const std::string& logtxt)
    {
        switch (_print_method)
        {
        case Screen:
            std::cout << logtxt;
            break;
        case OneFile:
            print_onefile(LOG_FILE, logtxt);
            break;
        case Class:
            print_classfile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void print_onefile(const std::string& logfile, const std::string& logtxt)
    {
        std::lock_guard<std::mutex> lg(_mtx);
        std::string filename = _print_path + logfile;
        int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void print_classfile(int level, const std::string& logtxt)
    {
        std::string filename = LOG_FILE;
        filename += '.';
        filename += level_to_string(level);
        print_onefile(filename, logtxt);
    }
private:
    int _print_method;
    std::string _print_path;
    std::mutex _mtx;
};